// Copyright Epic Games, Inc. All Rights Reserved.

#pragma once

#include <zenbase/refcount.h>
#include <zencore/iohash.h>
#include <zencore/logging.h>
#include <zencore/thread.h>
#include <zenhttp/httpclient.h>
#include <zenhttp/httpserver.h>

#include <atomic>
#include <chrono>
#include <list>
#include <memory>
#include <set>
#include <vector>

struct ZenCacheValue;

namespace cpr {
class Session;
}

namespace zen {

class CbObjectView;
class CloudCacheClient;
class IoBuffer;
struct IoHash;

/**
 * Cached access token, for use with `Authorization:` header
 */
struct CloudCacheAccessToken
{
	using Clock		= std::chrono::system_clock;
	using TimePoint = Clock::time_point;

	static constexpr int64_t ExpireMarginInSeconds = 30;

	std::string Value;
	TimePoint	ExpireTime;

	bool IsValid() const
	{
		return Value.empty() == false &&
			   ExpireMarginInSeconds < std::chrono::duration_cast<std::chrono::seconds>(ExpireTime - Clock::now()).count();
	}
};

struct CloudCacheResult
{
	IoBuffer	Response;
	uint64_t	SentBytes{};
	uint64_t	ReceivedBytes{};
	double		ElapsedSeconds{};
	int32_t		ErrorCode{};
	std::string Reason;
	bool		Success = false;
};

struct PutRefResult : CloudCacheResult
{
	std::vector<IoHash> Needs;
	IoHash				RawHash;
};

struct FinalizeRefResult : CloudCacheResult
{
	std::vector<IoHash> Needs;
};

struct CloudCacheExistsResult : CloudCacheResult
{
	std::set<IoHash> Needs;
};

struct GetObjectReferencesResult : CloudCacheResult
{
	std::set<IoHash> References;
};

/**
 * Context for performing Jupiter operations
 *
 * Maintains an HTTP connection so that subsequent operations don't need to go
 * through the whole connection setup process
 *
 */
class CloudCacheSession
{
public:
	CloudCacheSession(CloudCacheClient* CacheClient);
	~CloudCacheSession();

	CloudCacheResult Authenticate();
	CloudCacheResult GetRef(std::string_view Namespace, std::string_view BucketId, const IoHash& Key, ZenContentType RefType);
	CloudCacheResult GetBlob(std::string_view Namespace, const IoHash& Key);
	CloudCacheResult GetCompressedBlob(std::string_view Namespace, const IoHash& Key, std::filesystem::path TempFolderPath = {});
	CloudCacheResult GetObject(std::string_view Namespace, const IoHash& Key);
	CloudCacheResult GetInlineBlob(std::string_view		 Namespace,
								   std::string_view		 BucketId,
								   const IoHash&		 Key,
								   IoHash&				 OutPayloadHash,
								   std::filesystem::path TempFolderPath = {});

	PutRefResult	 PutRef(std::string_view Namespace, std::string_view BucketId, const IoHash& Key, IoBuffer Ref, ZenContentType RefType);
	CloudCacheResult PutBlob(std::string_view Namespace, const IoHash& Key, IoBuffer Blob);
	CloudCacheResult PutCompressedBlob(std::string_view Namespace, const IoHash& Key, IoBuffer Blob);
	CloudCacheResult PutCompressedBlob(std::string_view Namespace, const IoHash& Key, const CompositeBuffer& Blob);
	CloudCacheResult PutObject(std::string_view Namespace, const IoHash& Key, IoBuffer Object);

	FinalizeRefResult FinalizeRef(std::string_view Namespace, std::string_view BucketId, const IoHash& Key, const IoHash& RefHah);

	CloudCacheResult RefExists(std::string_view Namespace, std::string_view BucketId, const IoHash& Key);

	GetObjectReferencesResult GetObjectReferences(std::string_view Namespace, const IoHash& Key);

	CloudCacheResult BlobExists(std::string_view Namespace, const IoHash& Key);
	CloudCacheResult CompressedBlobExists(std::string_view Namespace, const IoHash& Key);
	CloudCacheResult ObjectExists(std::string_view Namespace, const IoHash& Key);

	CloudCacheExistsResult BlobExists(std::string_view Namespace, const std::set<IoHash>& Keys);
	CloudCacheExistsResult CompressedBlobExists(std::string_view Namespace, const std::set<IoHash>& Keys);
	CloudCacheExistsResult ObjectExists(std::string_view Namespace, const std::set<IoHash>& Keys);

	std::vector<IoHash> Filter(std::string_view Namespace, std::string_view BucketId, const std::vector<IoHash>& ChunkHashes);

	CloudCacheClient& Client() { return *m_CacheClient; };

private:
	inline LoggerRef Log() { return m_Log; }

	CloudCacheResult CacheTypeExists(std::string_view Namespace, std::string_view TypeId, const IoHash& Key);

	CloudCacheExistsResult CacheTypeExists(std::string_view Namespace, std::string_view TypeId, const std::set<IoHash>& Keys);

	LoggerRef				 m_Log;
	RefPtr<CloudCacheClient> m_CacheClient;
};

/**
 * Access token provider interface
 */
class CloudCacheTokenProvider
{
public:
	virtual ~CloudCacheTokenProvider() = default;

	virtual CloudCacheAccessToken AcquireAccessToken() = 0;

	static std::unique_ptr<CloudCacheTokenProvider> CreateFromStaticToken(CloudCacheAccessToken Token);

	struct OAuthClientCredentialsParams
	{
		std::string_view Url;
		std::string_view ClientId;
		std::string_view ClientSecret;
	};

	static std::unique_ptr<CloudCacheTokenProvider> CreateFromOAuthClientCredentials(const OAuthClientCredentialsParams& Params);

	static std::unique_ptr<CloudCacheTokenProvider> CreateFromCallback(std::function<CloudCacheAccessToken()>&& Callback);
};

struct CloudCacheClientOptions
{
	std::string_view		  Name;
	std::string_view		  ServiceUrl;
	std::string_view		  DdcNamespace;
	std::string_view		  BlobStoreNamespace;
	std::string_view		  ComputeCluster;
	std::chrono::milliseconds ConnectTimeout{5000};
	std::chrono::milliseconds Timeout{};
	bool					  AssumeHttp2 = false;
	bool					  AllowResume = false;
	uint8_t					  RetryCount  = 0;
};

/**
 * Jupiter upstream cache client
 */
class CloudCacheClient : public RefCounted
{
public:
	CloudCacheClient(const CloudCacheClientOptions& Options, std::unique_ptr<CloudCacheTokenProvider> TokenProvider);
	~CloudCacheClient();

	std::string_view DefaultDdcNamespace() const { return m_DefaultDdcNamespace; }
	std::string_view DefaultBlobStoreNamespace() const { return m_DefaultBlobStoreNamespace; }
	std::string_view ComputeCluster() const { return m_ComputeCluster; }
	std::string_view ServiceUrl() const { return m_HttpClient.GetBaseUri(); }

	LoggerRef Logger() { return m_Log; }

private:
	LoggerRef									   m_Log;
	const std::string							   m_DefaultDdcNamespace;
	const std::string							   m_DefaultBlobStoreNamespace;
	const std::string							   m_ComputeCluster;
	const std::unique_ptr<CloudCacheTokenProvider> m_TokenProvider;
	HttpClient									   m_HttpClient;

	friend class CloudCacheSession;
};

}  // namespace zen
